本文是《音视频编程》的一条支线。怎么回事呢?因为视频由三个部分组成:图像、音频和字幕。图像需要一个介质来显示。现在是开始学习Qt的好时机,因为很多OpenGL功能在Qt框架内不断发展。其中一个新的事情是QOpenGL··类的添加(QGL··类的替代品)。我这里的介质选择的是Qt的QOpenGLWidget,这里的原因不再展开来讲。本系列不局限于音视频的应用,如果对opengl和Qt封装的OpenGL api感兴趣,倒是可以一观。

Qt使用的是5.9.5版本,编译器为MSVC2015_32和MingW53_32(Qt5.9.5自带),我会分别在两种编译器下编译通过。以后除例外不再特别说。本系列会刻意的不使用类似freeglut、glfw、glew、glad等等第三方gl工具或者库,这样做的目的我暂时是这么考虑的:如果尽可能的少依赖其他库或工具,尽量只用OpenGL和Qt封装的GL api,这样会容易编译在其他平台,不过带来的问题是,依赖Qt的紧密度会增强甚至不可或缺。和以往一样,我这里假设你Qt/C++的水平起码是个基础,如果了解OpenGL api将更好的帮助你阅读本系列。

###前言

为了方便起见,我会选择将 QOpenGLWidget 和 QOpenGLFunctions 一块继承。在Qt的目前版本中,不再需要glew等一些列其他第三方gl工具或库,并且 QOpenGLFunctions 默认为允许访问 OpenGL ES 2.0 API 结构。如果你不想通过继承子类来访问 QOpenGLFunctions 的功能,你可以通过QOpenGLContext获取可用的函数。

1
2
3
4
5
QOpenGLFunctions functions(QOpenGLContext::currentContext());
functions.glFunctionHere();
// or...
QOpenGLFunctions *functions = QOpenGLContext::currentContext()->functions();
functions->glFunctionHere();

QOpenGLFunctions 默认为 OpenGL ES 2.0的原因是出于兼容性原因,所以我们可以轻松地将产品移植到嵌入式平台(如Android和iOS)。我们的例子将使用这个函数上下文,但我们也可以要求特定的函数上下文(例如QOpenGLFunctions_4_3_Core)。除了OpenGL ES 2.0 API之外,我们没有任何需求,所以我将坚持使用QOpenGLFunctions 类。

1
2
3
4
5
6
7
8
9
10
11
class HsOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit HsOpenGLWidget(QWidget* parent = nullptr);

protected:
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
};

###创建窗口

我们只需要创建我们的类继承自 QOpenGLWidget 和 QOpenGLFunctions,并且重写以下三个虚函数。顾名思义,这三个函数分别起到OpenGL上下文执行初始化、绘制和检测窗体大小变化的功能。接下来我们试着实现这三个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void HsOpenGLWidget::initializeGL()
{
// 初始化当前上下文的OpenGL函数
initializeOpenGLFunctions();

// glClearColor函数是一个状态设置函数
// 设置 glClear 的时候的颜色值,本身不会进行任何 clear 操作
// 指定刷新颜色缓冲区时所用的颜色
// 作用是,防止缓冲区中原有的颜色信息影响本次绘图
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
}

void HsOpenGLWidget::paintGL()
{
// glClear函数则是一个状态应用的函数
// 清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
}

void HsOpenGLWidget::resizeGL(int w, int h)
{
//OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。
//例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。
//注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。
glViewport(0, 0, w, h);
}

###后话

如果你想知道你的上下文使用的opengl是什么版本的话,你可以使用以下代码(本文代码由于过于简单,这里不再进行太多解释)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    //print Context Information
QString glType;
QString glVersion;
QString glProfile;

// Get Version Information
glType = (context()->isOpenGLES()) ? "OpenGL ES" : "OpenGL";
glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));

// Get Profile Information
#define CASE(c) case QSurfaceFormat::c: glProfile = #c; break
switch (format().profile())
{
CASE(NoProfile);
CASE(CoreProfile);
CASE(CompatibilityProfile);
}
#undef CASE

// qPrintable() will print our QString w/o quotes around it.
qDebug() << qPrintable(glType) << qPrintable(glVersion) << "(" << qPrintable(glProfile) << ")";

我通过打印发现我这里输出 “OpenGL 4.4.0 - Build 20.19.15.4300 ( CompatibilityProfile )”,我通过编译(什么鬼?Qt的项目还能编译在安卓上?是的你没有看错,如果有兴趣请看我的这篇博客 Qt5搭建Android开发环境)在我的小米5(Android 7.0)手机上发现,输出的是“OpenGL ES 2.0 bulid 1.3···”,从这里可以知道,Qt将openg api封装的更适宜跨平台。如果我想规定使用 OpenGL 3.3 的话,我们需要在构造函数里添加如下代码。如果不更换环境的话,这可能是我们唯一测试以上输出版本的途径了。不过我不这么写,我没有必须使用3.3或者其他特定版本的需求。

1
2
3
4
5
QSurfaceFormat format;
format.setRenderableType(QSurfaceFormat::OpenGL);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setVersion(3,3);
this->setFormat(format);